package org.streets.eis.component.datetime;

import java.lang.reflect.Method;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.wicket.Component;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.ResourceReference;
import org.apache.wicket.Response;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.AbstractBehavior;
import org.apache.wicket.markup.html.IHeaderContributor;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.util.lang.Objects;
import org.apache.wicket.util.string.Strings;

import org.streets.eis.component.WebResources;

public class DatePickerBehavior extends AbstractBehavior implements IHeaderContributor{	
	private static final long serialVersionUID = 1L;
	
	private static Method GETINSTANCEMETHOD = null;

	static {
		try{
			GETINSTANCEMETHOD = DateFormatSymbols.class.getMethod("getInstance",
					new Class[] { Locale.class });
		}catch (Exception e){
			// ignore
		}
	}

	//yui calendar's format
	public static String FORMAT_DATE = "MM/dd/yyyy";
	public static String FORMAT_PAGEDATE = "MM/yyyy";
	
	private FormComponent<?> component;

	public DatePickerBehavior(){
	}

	@Override
	public void bind(Component component){
		if (!(component instanceof FormComponent)){
			throw new RuntimeException("DatePickerBehavior cannot bind with not FormComponent.");
		}
		this.component = (FormComponent<?>)component;
		component.setOutputMarkupId(true);
	}

	private void renderIcon(Response response){
		response.write("<img width=\"16px\" height=\"16px\" style=\"");
		response.write(getIconStyle());
		response.write("\" id=\"");
		response.write(getIconId());
		response.write("\" src=\"");
		CharSequence iconUrl = getIconUrl();
		response.write(Strings.escapeMarkup(iconUrl != null ? iconUrl.toString() : ""));
		response.write("\" alt=\"");
		CharSequence alt = getIconAltText();
		response.write(Strings.escapeMarkup((alt != null) ? alt.toString() : ""));
		response.write("\" title=\"");
		CharSequence title = getIconTitle();
		response.write(Strings.escapeMarkup((title != null) ? title.toString() : ""));
		response.write("\"/>");
	}

	@Override
	public void onRendered(Component component){
		super.onRendered(component);
		Response response = component.getResponse();
		
		renderIcon(response);
		
		response.write("<span class=\"yui-skin-sam\"><span style=\"");
		if (renderOnLoad()){
			response.write("display:block;");
		} else{
			response.write("display:none;");
			response.write("position:absolute;");
		}
		response.write("z-index: 100;\" id=\"");
		response.write(getEscapedComponentMarkupId());
		response.write("Dp\"></span>");

		if (renderOnLoad()){
			response.write("<br style=\"clear:left;\"/>");
		}
		response.write("</span>");
	}


	@Override
	public void renderHead(IHeaderResponse response){
		response.renderJavascriptReference(WebResources.Yahoo.CORE_JS);
		response.renderJavascriptReference(WebResources.Yahoo.DOM_JS);
		response.renderJavascriptReference(WebResources.Yahoo.EVENT_JS);
		response.renderJavascriptReference(WebResources.Yahoo.CALENDAR_JS);
		response.renderJavascriptReference(WebResources.Streets.Calendar.DATETIME_JS);
		response.renderCSSReference(WebResources.Streets.Calendar.CALENDAR_CSS);
		
		Map<String, Object> p = new HashMap<String, Object>();
		configure(p);
		if (!p.containsKey("navigator") && enableMonthYearSelection()){
			p.put("navigator", Boolean.TRUE);
		}

		if (enableMonthYearSelection() && p.containsKey("pages") &&
				Objects.longValue(p.get("pages")) > 1){
			throw new IllegalStateException(
					"You cannot use a CalendarGroup with month/year selection!");
		}		
		StringBuffer calendarInit = new StringBuffer();
		for (Iterator<Entry<String, Object>> i = p.entrySet().iterator(); i.hasNext();){
			Entry<String, Object> entry = i.next();
			calendarInit.append(entry.getKey());
			Object value = entry.getValue();
			if (value instanceof CharSequence){
				calendarInit.append(":\"");
				calendarInit.append(Strings.toEscapedUnicode(value.toString()));
				calendarInit.append("\"");
			}else if (value instanceof CharSequence[]) {
				calendarInit.append(":[");
				CharSequence[] valueArray = (CharSequence[])value;
				for (int j = 0; j < valueArray.length; j++){
					CharSequence tmpValue = valueArray[j];
					if (j > 0){
						calendarInit.append(",");
					}
					if (tmpValue != null){
						calendarInit.append("\"");
						calendarInit.append(Strings.toEscapedUnicode(tmpValue.toString()));
						calendarInit.append("\"");
					}
				}
				calendarInit.append("]");
			} else if (value instanceof Map){
				calendarInit.append(":");
				@SuppressWarnings("unchecked")
				Map<String, Object> map = (Map<String, Object>)value;
				appendMapping(map, calendarInit);
			} else {
				calendarInit.append(":");
				calendarInit.append(Strings.toEscapedUnicode(String.valueOf(value)));
			}
			if (i.hasNext()){
				calendarInit.append(",");
			}
		}		
		
		StringBuffer datePickerJs = new StringBuffer();
		String widgetId = getEscapedComponentMarkupId();
		datePickerJs
			.append("Streets.DateTime.init( {")
			.append("widgetId: \"").append(widgetId).append("\",")
			.append("componentId: \"").append(getComponentMarkupId()).append("\",")				
			.append("calendarInit: { ").append(calendarInit).append(" },")
			.append("datePattern: \"").append(getDatePattern()).append("\",")
			.append("alignWithInput: ").append(alignWithInput()).append(",")
			.append("fireChangeEvent: ").append(notifyComponentOnDateSelected()).append(",")
			.append("hideOnSelect: ").append(hideOnSelect())
			.append("});");

		String script = getAdditionalJavascript();
		if (script != null)	{
			datePickerJs.append(Strings.replaceAll(script, "${calendar}","YAHOO.streets." + widgetId + "DpJs"));
		}
		
		response.renderOnDomReadyJavascript(datePickerJs.toString());

		if (AjaxRequestTarget.get() != null){
			final String javascript = "var e = Wicket.$('" + getEscapedComponentMarkupId() + "Dp" +
					"'); if (e != null && typeof(e.parentNode) != 'undefined' && " +
					"typeof(e.parentNode.parentNode != 'undefined')) " +
					"e.parentNode.parentNode.removeChild(e.parentNode);";

			response.renderJavascript(javascript, null);
		}
	}

	private void setWidgetProperty(Map<String, Object> widgetProperties, String key, String[] array){
		if (array != null && array.length > 0){
			widgetProperties.put(key, array);
		}
	}

	protected boolean alignWithInput(){
		return true;
	}

	protected void configure(Map<String, Object> widgetProperties){
		widgetProperties.put("close", true);
		localize(widgetProperties);
		Object modelObject = component.getDefaultModelObject();

		if (modelObject instanceof Date){
			Date date = (Date)modelObject;
			widgetProperties.put("selected", new SimpleDateFormat(FORMAT_DATE).format(date));
			widgetProperties.put("pagedate", new SimpleDateFormat(FORMAT_PAGEDATE).format(date));
		}		
	}

	protected final String[] filterEmpty(String[] array){
		if (array == null){
			return null;
		}
		List<String> l = new ArrayList<String>(array.length);
		for (int i = 0; i < array.length; i++){
			if (!Strings.isEmpty(array[i])){
				l.add(array[i]);
			}
		}
		return l.toArray(new String[l.size()]);
	}

	protected final String getComponentMarkupId(){
		return component.getMarkupId();
	}

	protected String getDatePattern(){
		String format = null;
		if (component instanceof DateTimeField){
			format = "yyyy-MM-dd HH:mm:ss";
		} else {
			format = "yyyy-MM-dd";
		}
		return format;
	}

	protected final String getEscapedComponentMarkupId(){
		return component.getMarkupId().replaceAll("\\W", "");
	}

	protected final String getIconId(){
		return getEscapedComponentMarkupId() + "Icon";
	}

	protected String getIconStyle(){
		return "cursor: pointer; border: none;position:absolute;visibility:hidden;z-index:1;";
	}

	protected CharSequence getIconTitle(){
		return "点击选择日期";
	}

	protected CharSequence getIconAltText(){
		return "选择";
	}

	protected CharSequence getIconUrl(){
		return RequestCycle.get().urlFor(new ResourceReference(WebResources.class, "res/date-selector.gif"));
	}

	protected Locale getLocale(){
		return component.getLocale();
	}

	protected void localize(Map<String, Object> widgetProperties){
		DateFormatSymbols dfSymbols = null;
		if (GETINSTANCEMETHOD != null){
			try{
				dfSymbols = (DateFormatSymbols)GETINSTANCEMETHOD.invoke(null,
						new Object[] { getLocale() });
			} catch (Exception e){
				// ignore
			}
		}
		if (dfSymbols == null){
			dfSymbols = new DateFormatSymbols(getLocale());
		}

		setWidgetProperty(widgetProperties, "MONTHS_SHORT", filterEmpty(dfSymbols.getShortMonths()));
		setWidgetProperty(widgetProperties, "MONTHS_LONG", filterEmpty(dfSymbols.getMonths()));
		setWidgetProperty(widgetProperties, "WEEKDAYS_MEDIUM", filterEmpty(dfSymbols.getShortWeekdays()));
		setWidgetProperty(widgetProperties, "WEEKDAYS_LONG", filterEmpty(dfSymbols.getWeekdays()));

		widgetProperties.put("START_WEEKDAY", new Integer(Calendar.getInstance(getLocale()).getFirstDayOfWeek() - 1));

		if (Locale.SIMPLIFIED_CHINESE.equals(getLocale()) ||	Locale.TRADITIONAL_CHINESE.equals(getLocale())){
			setWidgetProperty(widgetProperties, "WEEKDAYS_1CHAR", filterEmpty(substring(dfSymbols.getShortWeekdays(), 2, 1)));
			widgetProperties.put("WEEKDAYS_SHORT", filterEmpty(substring(dfSymbols	.getShortWeekdays(), 2, 1)));
			widgetProperties.put("MY_LABEL_YEAR_POSITION",1);
			widgetProperties.put("MY_LABEL_MONTH_POSITION",1);
			widgetProperties.put("MY_LABEL_YEAR_SUFFIX","年 ");
			
			widgetProperties.put("START_WEEKDAY",1);
			if (enableMonthYearSelection()){
				Map<String,Map<String,String>> navigator = new HashMap<String,Map<String,String>>();
				Map<String,String> strings = new HashMap<String,String>();
				navigator.put("strings", strings);
				strings.put("month", "月份");
				strings.put("year", "年份");
				strings.put("submit", "确定");
				strings.put("cancel", "放弃");
				strings.put("invalidYear", "年份请输入数字!");
				widgetProperties.put("navigator", navigator);
			}
		} else{
			setWidgetProperty(widgetProperties, "WEEKDAYS_1CHAR", filterEmpty(substring(dfSymbols.getShortWeekdays(), 0, 1)));
			setWidgetProperty(widgetProperties, "WEEKDAYS_SHORT", filterEmpty(substring(dfSymbols.getShortWeekdays(), 0, 2)));
		}		
	}

	protected boolean notifyComponentOnDateSelected(){
		return true;
	}

	protected final String[] substring(final String[] array, final int len){
		return substring(array, 0, len);
	}

	protected final String[] substring(final String[] array, final int start, final int len){
		if (array != null){
			String[] copy = new String[array.length];
			for (int i = 0; i < array.length; i++){
				String el = array[i];
				if (el != null){
					if (el.length() >= (start + len)){
						copy[i] = el.substring(start, start + len);
					} else {
						copy[i] = el;
					}
				}
			}
			return copy;
		}
		return null;
	}

	protected boolean enableMonthYearSelection(){
		return true;
	}

	protected boolean hideOnSelect(){
		return true;
	}

	protected boolean renderOnLoad(){
		return false;
	}

	protected String getAdditionalJavascript(){
		return "";
	}

	@Override
	public boolean isEnabled(Component component){
		return component.isEnabledInHierarchy();
	}

	private void appendMapping(Map<String, ? > map, StringBuffer calendarInit){
		boolean first = true;
		calendarInit.append("{");
		for (String key : map.keySet()){
			if (first){
				first = false;
			} else {
				calendarInit.append(",");
			}
			calendarInit.append(Strings.toEscapedUnicode(key));
			calendarInit.append(":");
			
			if (map.get(key) instanceof Map)	{
				@SuppressWarnings("unchecked")
				Map<String, ? > value = (Map<String, ? >)map.get(key);
				appendMapping(value, calendarInit);
			} else{
				calendarInit.append("\"");
				calendarInit.append(Strings.toEscapedUnicode(map.get(key).toString()));
				calendarInit.append("\"");
			}
		}
		calendarInit.append("}");
	}
	
}
